﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Database;
using Microsoft.Xna.Framework;

namespace TrafficLightSim
{
    //main class that regulates the whole simulation
    public class Simulation
    {
        #region Field Region

        List<Lane> hLanes = new List<Lane>(); //list of horizontal lanes in the simulation
        List<Lane> vLanes = new List<Lane>(); //list of vertical lanes in sim
        List<Car> cars = new List<Car>(); //list of cars in the simulation
        TrafficLight[] trafficLight; //array of traffic lights
        int simRun = 1, hLane = 1, vLane = 1; //how many time the simulation is run, how many hLanes are there, how many vLanes are there
        double hProb = 0.5; //probability of car entering from h lane
        double vProb = 0.5; //prob of car entering from v lane
        Rectangle intersection, simulationRect; //rectangle covering the intersection rect, rectangle of the simulation screen
        Vector2 cDownSize = new Vector2(35, 55);
        Vector2 cRightSize = new Vector2(55, 35);
        bool isTwoWay = false;

        List<CarData> cDat = new List<CarData>();
        SaveData saveDat;
        #endregion

        #region Property Region

        public List<CarData> CDat
        {
            get { return cDat; }
        }

        public SaveData SaveDat
        {
            get { return saveDat; }
        }

        public bool IsTwoWay
        {
            get { return isTwoWay; }
            set { isTwoWay = value; }
        }

        public List<Lane> HLanes
        {
            get { return hLanes; }
        }

        public List<Lane> VLanes
        {
            get { return vLanes; }
        }

        public int HLane
        {
            get { return hLane; }
            set { hLane = value; }
        }

        public int VLane
        {
            get { return vLane; }
            set { vLane = value; }
        }

        public TrafficLight[] TrafficLight
        {
            get { return trafficLight; }
            set { trafficLight = value; }
        }

        public int SimRun
        {
            get { return simRun; }
            set { simRun = value; }
        }

        public double HProb
        {
            get { return hProb; }
            set { hProb = value; }
        }

        public double VProb
        {
            get { return vProb; }
            set { vProb = value; }
        }

        public List<Car> Cars
        {
            get { return cars; }
        }

        #endregion

        #region Method Region

        public void GenerateCars(double time)
        {
            int rand = 2;
            if (time % 500 <= 20) //generate cars every few ms
            {
                int count = 0;
                if (isTwoWay)
                    rand = 4; //used for random number gneerator underneath here
                else rand = 2;
                Car car = new Car(); //instantiate a new car object

                Random r = new Random();
                int speed = r.Next(3, 8);
                int temp = r.Next(rand); //determine which way the car would be moving towards, if 0 means car going down, 1 going right
                if (temp == 0)
                {
                    car.Direction = Direction.Down; //down
                    car.Size = cDownSize;
                    car.Speed = speed;
                    int pos = r.Next(vLanes.Count);
                    while (vLanes[pos].Direction != Direction.Down) //to ensures that car can only b generated if its on the right lane
                    {
                        count++;
                        pos = r.Next(vLanes.Count);
                        if (count >= 20) //if has tried 20 times and still doesn't get any good rseult, just break out of loop
                            return;
                    }
                    car.Position = new Vector2(vLanes[pos].LaneRect.Left, -50);
                }
                else if (temp == 1)
                {
                    car.Direction = Direction.Right; //right
                    car.Size = cRightSize;
                    car.Speed = speed;
                    int pos = r.Next(hLanes.Count);
                    while (hLanes[pos].Direction != Direction.Right)
                    {
                        count++;
                        pos = r.Next(hLanes.Count);
                        if (count >= 20)
                            return;
                    }
                    car.Position = new Vector2(-50, hLanes[pos].LaneRect.Top);
                }
                else if (temp == 2) //for going upwards
                {
                    car.Direction = Direction.Up; //up
                    car.Size = cDownSize;
                    int pos = r.Next(vLanes.Count);
                    while (vLanes[pos].Direction != Direction.Up)
                    {
                        count++;
                        pos = r.Next(vLanes.Count);
                        if (count >= 20)
                            return;
                    }
                    car.Position = new Vector2(vLanes[pos].LaneRect.Left, 500);
                }
                else if (temp == 3)
                {
                    car.Direction = Direction.Left;
                    car.Size = cRightSize;
                    int pos = r.Next(hLanes.Count);
                    while (hLanes[pos].Direction != Direction.Left)
                    {
                        count++;
                        pos = r.Next(hLanes.Count);
                        if (count >= 20)
                            return;
                    }
                    car.Position = new Vector2(Game1.GameRef.ScreenWidth, hLanes[pos].LaneRect.Top);
                }

                #region Probability code
                if ((hProb == 0 && (car.Direction == Direction.Right || car.Direction == Direction.Left)) ||
                    (vProb == 0 && (car.Direction == Direction.Down || car.Direction == Direction.Up))) //if prob is 0, then car will never appear
                    car.WillAppear = false;
                else if ((hProb == 1 && (car.Direction == Direction.Right || car.Direction == Direction.Left)) ||
                    (vProb == 1 && (car.Direction == Direction.Down || car.Direction == Direction.Up))) //if prob is 1, then it will always appear
                    car.WillAppear = true;
                else //codes for the prob between 0.1 to 0.9, don't touch this.
                {
                    int probability = r.Next(4);
                    switch (probability)
                    {
                        case 0:
                            if ((hProb >= 0.1 && hProb <= 0.3 && (car.Direction == Direction.Right || car.Direction == Direction.Left)) ||
                                (vProb >= 0.1 && vProb <= 0.3 && (car.Direction == Direction.Down || car.Direction == Direction.Up)))
                                car.WillAppear = false;
                            else if ((hProb >= 0.4 && (car.Direction == Direction.Right  || car.Direction == Direction.Left)) ||
                                (vProb >= 0.4 && (car.Direction == Direction.Down || car.Direction == Direction.Up)))
                                car.WillAppear = true;
                            break;
                        case 1:
                            if ((hProb >= 0.1 && hProb <= 0.8 && (car.Direction == Direction.Right || car.Direction == Direction.Left)) ||
                                (vProb >= 0.1 && vProb <= 0.8 && (car.Direction == Direction.Down || car.Direction == Direction.Up)))
                                car.WillAppear = false;
                            else car.WillAppear = true;
                            break;
                        case 2:
                            if ((hProb >= 0.1 && hProb <= 0.3 && (car.Direction == Direction.Right || car.Direction == Direction.Left)) ||
                                (vProb >= 0.1 && vProb <= 0.3 && (car.Direction == Direction.Down || car.Direction == Direction.Up)))
                                car.WillAppear = false;
                            else if ((hProb >= 0.4 && (car.Direction == Direction.Right || car.Direction == Direction.Left)) ||
                                (vProb >= 0.4 && (car.Direction == Direction.Down || car.Direction == Direction.Up)))
                                car.WillAppear = true;
                            break;
                        case 3:
                            if ((hProb >= 0.1 && hProb <= 0.3 && (car.Direction == Direction.Right || car.Direction == Direction.Left)) ||
                                (vProb >= 0.1 && vProb <= 0.3 && (car.Direction == Direction.Down || car.Direction == Direction.Up)))
                                car.WillAppear = true;
                            else if ((hProb >= 0.4 && (car.Direction == Direction.Right || car.Direction == Direction.Left)) ||
                                (vProb >= 0.4 && (car.Direction == Direction.Down || car.Direction == Direction.Up)))
                                car.WillAppear = false;
                            break;
                        default: car.WillAppear = false;
                            break;
                    }
                }
                #endregion
                if (car.WillAppear) //if car succeeds in going through "elimination", then it'll appear, added to the list
                    cars.Add(car);
            }
        }

        public void Initialize()
        {
            trafficLight = new TrafficLight[4] { new TrafficLight(), new TrafficLight(LightState.Red), new TrafficLight(), new TrafficLight(LightState.Red)};
            trafficLight[0].DirectionControl = Direction.Down;
            trafficLight[1].DirectionControl = Direction.Right;
            trafficLight[2].DirectionControl = Direction.Up;
            trafficLight[3].DirectionControl = Direction.Left;        

            simulationRect = new Rectangle(0, 0, Game1.GameRef.ScreenWidth, Game1.GameRef.SimHeight);

        }

        //update the cars, as in, if position of the car is nearing the end of lane, remove from the list so it wouldn't be drawn
        //and would be eligible for gc
        public void UpdateCarList()
        {
            foreach (Car c in cars)
            {
                if (c.Position.X >= Game1.GameRef.ScreenWidth)
                {
                    cars.Remove(c);
                    break;
                }
                else if (c.BoundRect.Bottom >= Game1.GameRef.SimHeight)
                {
                    cars.Remove(c);
                    break;
                }

                if (c.IsInAccident)
                {
                    cars.Remove(c);
                    break;
                }
            }
        }

        public void ToggleTrafficLight()
        {
            for (int i = 0; i < 4; i++)
                trafficLight[i].ChangeState();
        }

        public void UpdateSimulation(double time)
        {
            
            GenerateCars(time); //generate cars           
            foreach (Car c in Cars)
            {
                if (!c.BoundRect.Intersects(intersection)) //if the car isn't near the intersection
                {
                    foreach (Car c2 in cars) //loop through all elements in cars
                    {
                        //check if the car object we're gonna compare is NOT the same object as the car (c)
                        if (c.BoundRect != c2.BoundRect)
                        {
                            //if it's not the same, then check if the car's boundRect contains a point that is the other car's edge
                            if (c.BoundRect.Contains(new Point(c2.BoundRect.Left, c2.BoundRect.Top)) &&
                                (c.Direction == Direction.Right || c.Direction == Direction.Down))
                            {
                                c.CanMove = false; //if it does contain the point, car can't move
                                break; //break out of loop cause you don't need to check other cars
                            }
                            else if (c.BoundRect.Contains(new Point(c2.BoundRect.Right, c2.BoundRect.Top)) &&
                                (c.Direction == Direction.Left))
                            {
                                c.CanMove = false;
                                break;
                            }
                            else if ((c.Direction == Direction.Up) && (c.BoundRect.Contains(new Point(c2.BoundRect.Left, c2.BoundRect.Bottom))))
                            {
                                c.CanMove = false;
                                break;
                            }
                            else
                            { //otherwise, it can move
                                c.CanMove = true;
                            }
                        }
                    }

                    #region overtaking region

                    if (!c.CanMove && !isTwoWay) //if car can't move forward
                    {

                        if (c.Direction == Direction.Right || c.Direction == Direction.Left) //if car direction is to the right
                        {
                            //check if top/Down is empty, to ensure that car can move upwards and downwards
                            int tempUp = (int)c.Position.Y - Lane.size; //this'll be the car position if it overtakes 'up'
                            int tempDown = (int)c.Position.Y + Lane.size; //if overtakes 'down'
                            foreach (Car c3 in cars)
                            {
                                if (c.BoundRect.Y != c3.BoundRect.Y) //only check if this car is on another lane on hlane!
                                {
                                    Rectangle testRectU = new Rectangle(c.BoundRect.X, tempUp, (int)c.Size.X, (int)c.Size.Y); //new 'up' rectangle
                                    Rectangle testRectD = new Rectangle(c.BoundRect.X, tempDown, (int)c.Size.X, (int)c.Size.Y); //new 'down' rect
                                    //here, the first two lines are to check if there's a car/not right under the car that's gonna do overtaking
                                    //whereas the 2 last lines are to ensure that the car would only do overtaking if there's no car IN FRONT of the
                                    //lane it's gonna overtake to
                                    if (c3.BoundRect.Contains(new Point((int)c.Position.X, tempUp)) ||
                                        c3.BoundRect.Contains(new Point((int)c.Position.X, tempDown)) ||
                                        testRectU.Contains(new Point(c3.BoundRect.Left, c3.BoundRect.Top)) ||
                                        testRectD.Contains(new Point(c3.BoundRect.Left, c3.BoundRect.Top)))
                                    {
                                        c.Overtake = false;
                                        break;
                                    }
                                    else
                                    {
                                        foreach (Lane l in hLanes) //all the ifs here are to ensure that car would not go out of lane
                                        {
                                            if (l.LaneRect.Top == tempUp)
                                            {
                                                c.Overtake = true;
                                                c.OvertakeDirection = Direction.Up;
                                                c.OvertakeDist = tempUp;
                                                break;
                                            }
                                            else if (l.LaneRect.Top == tempDown)
                                            {
                                                c.Overtake = true;
                                                c.OvertakeDirection = Direction.Down;
                                                c.OvertakeDist = tempDown;
                                                break;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        //same thing applies for going downwards like going right! just some diff variables
                        else if (c.Direction == Direction.Down)
                        {

                            int tempLeft = (int)c.Position.X - Lane.size;
                            int tempRight = (int)c.Position.X + Lane.size;
                            foreach (Car c3 in cars)
                            {
                                if (c.BoundRect.X != c3.BoundRect.X)
                                {
                                    Rectangle testRectL = new Rectangle(tempLeft, c.BoundRect.Y, (int)c.Size.X, (int)c.Size.Y);
                                    Rectangle testRectR = new Rectangle(tempRight, c.BoundRect.Y, (int)c.Size.X, (int)c.Size.Y);
                                    if (c3.BoundRect.Contains(new Point(tempLeft, (int)c.Position.Y)) ||
                                        c3.BoundRect.Contains(new Point(tempRight, (int)c.Position.Y)) ||
                                        testRectL.Contains(new Point(c3.BoundRect.Left, c3.BoundRect.Top)) ||
                                        testRectR.Contains(new Point(c3.BoundRect.Left, c3.BoundRect.Top)))
                                    {
                                        c.Overtake = false;
                                        break;
                                    }
                                    else
                                    {
                                        foreach (Lane l in vLanes)
                                        {
                                            if (l.LaneRect.Left == tempLeft)
                                            {
                                                c.Overtake = true;
                                                c.OvertakeDirection = Direction.Up;
                                                c.OvertakeDist = tempLeft;
                                                break;
                                            }
                                            else if (l.LaneRect.Left == tempRight)
                                            {
                                                c.Overtake = true;
                                                c.OvertakeDirection = Direction.Down;
                                                c.OvertakeDist = tempRight;
                                                break;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                    #endregion
                    if (c.Overtake)
                    {
                        if (c.Direction == Direction.Right)
                            c.Position = new Vector2(c.Position.X, c.OvertakeDist);
                        else if (c.Direction == Direction.Down)
                            c.Position = new Vector2(c.OvertakeDist, c.Position.Y);
                        c.BoundRect = new Rectangle((int)c.Position.X, (int)c.Position.Y, (int)c.Size.X, (int)c.Size.Y);
                        c.Overtake = false;
                    }

                    if (c.CanMove)
                        c.MoveCar();
                }
                else if (c.BoundRect.Intersects(intersection))
                {
                    //if the car is going right: if 3/4 of the car is inside intersection, keep moving!
                    if (((trafficLight[1].State == LightState.Green || intersection.Contains(new Point(c.BoundRect.Left, c.BoundRect.Top))
                            || ((intersection.Left - c.BoundRect.Left) < (c.Size.X * 3 / 4)))
                            && c.Direction == Direction.Right) ||
                        ((TrafficLight[0].State == LightState.Green || intersection.Contains(new Point(c.BoundRect.Right, c.BoundRect.Top))
                            || ((intersection.Top - c.BoundRect.Top) < (c.Size.Y * 3 / 4)))
                            && c.Direction == Direction.Down) ||
                        ((trafficLight[2].State == LightState.Green || intersection.Contains(new Point(c.BoundRect.Left, c.BoundRect.Bottom))
                            || ((c.BoundRect.Top - intersection.Bottom) > (c.Size.Y * 1 / 4)))
                            && c.Direction == Direction.Up) ||
                        ((trafficLight[3].State == LightState.Green || intersection.Contains(new Point(c.BoundRect.Right, c.BoundRect.Top))
                            || ((intersection.Right - c.BoundRect.Left) > (c.Size.X * 1 / 4)))
                            && c.Direction == Direction.Left) ||
                        intersection.Contains(c.BoundRect))
                            c.MoveCar();
                }
            }

            //something is so wrong here :)
            foreach (Car c in cars)
            {
                foreach (Car c2 in cars)
                {
                    if (c.BoundRect != c2.BoundRect)
                    {
                        if (c.BoundRect.Contains(c2.BoundRect))
                        {
                            c.IsInAccident = true;
                            c2.IsInAccident = true;
                        }
                    }
                }
            }

            UpdateCarList(); //update the car if it has reached the end of the lane
        }

        //update the data of the lanes
        public void UpdateLanes()
        {
            hLanes.Clear();
            vLanes.Clear();
            while (hLanes.Count < hLane)
            {
                Lane lane = new Lane(Direction.Right);
                lane.LaneRect = new Rectangle(0, (Game1.GameRef.SimHeight / 2) - (hLane * Lane.size / 2) + (hLanes.Count * Lane.size), Game1.GameRef.ScreenWidth, Lane.size);
                hLanes.Add(lane);
            }

            while (vLanes.Count < vLane)
            {
                Lane lane = new Lane(Direction.Down);
                lane.LaneRect = new Rectangle((Game1.GameRef.ScreenHeight / 2) - (vLane * Lane.size / 2) + (vLanes.Count * Lane.size), 0, Lane.size, Game1.GameRef.SimHeight);
                vLanes.Add(lane);
            }

            intersection = new Rectangle(vLanes.First().LaneRect.Left,
                hLanes.First().LaneRect.Top,
                vLanes.Count * Lane.size,
                hLanes.Count * Lane.size); //this intersection rectangle is a rectangle that covers the intersection of both lanes
            if (hLanes.Count > 1 && isTwoWay)
            {
                int hLaneCount = hLanes.Count / 2;
                for (int i = 0; i < hLaneCount; i++)
                {
                    hLanes[i].Direction = Direction.Right;
                }
                for (int i = hLaneCount; i < hLanes.Count; i++)
                {
                    hLanes[i].Direction = Direction.Left;
                }
            }

            if (vLanes.Count > 1 && isTwoWay)
            {
                int vLaneCount = vLanes.Count / 2;
                for (int i = 0; i < vLaneCount; i++)
                {
                    vLanes[i].Direction = Direction.Down;
                }
                for (int i = vLaneCount; i < vLanes.Count; i++)
                {
                    vLanes[i].Direction = Direction.Up;
                }
            }
            
        }

        public void FlushSimData()
        {
            cars.Clear();
            Initialize();
        }

        internal void LoadSim(List<CarData> carData, SaveData saveData)
        {
            hLane = saveData.hLane;
            vLane = saveData.vLane;
            simRun = saveData.simNum;
            hProb = saveData.hProb;
            vProb = saveData.vProb;
            if (saveData.simMode == "Normal")
                isTwoWay = false;
            else isTwoWay = true;

            if (saveData.trafficHLane == "Red")
            {
                trafficLight[1].State = LightState.Red;
                trafficLight[3].State = LightState.Red;
            }
            else if (saveData.trafficHLane == "Yellow")
            {
                trafficLight[1].State = LightState.Yellow;
                trafficLight[3].State = LightState.Yellow;
            }
            else if (saveData.trafficHLane == "Green")
            {
                trafficLight[1].State = LightState.Green;
                trafficLight[3].State = LightState.Green;
            }

            if (saveData.trafficVLane == "Red")
            {
                trafficLight[0].State = LightState.Red;
                trafficLight[2].State = LightState.Red;
            }
            else if (saveData.trafficVLane == "Yellow")
            {
                trafficLight[0].State = LightState.Yellow;
                trafficLight[2].State = LightState.Yellow;
            }
            else if (saveData.trafficVLane == "Green")
            {
                trafficLight[0].State = LightState.Green;
                trafficLight[2].State = LightState.Green;
            }

            foreach (CarData cd in carData)
            {
                Car c = new Car();
                c.Position = new Vector2(cd.xPos, cd.yPos);
                c.Speed = cd.speed;
                if (cd.direction == "Up")
                {
                    c.Direction = Direction.Up;
                    c.Size = cDownSize;
                }
                else if (cd.direction == "Down")
                {
                    c.Direction = Direction.Down;
                    c.Size = cDownSize;
                }
                else if (cd.direction == "Right")
                {
                    c.Direction = Direction.Right;
                    c.Size = cRightSize;
                }
                else if (cd.direction == "Left")
                {
                    c.Direction = Direction.Left;
                    c.Size = cRightSize;
                }
                cars.Add(c);
            }

        }

        //convert the needed data to a save data
        public void SaveDataExec()
        {
            int x = 1;
            foreach (Car c in cars)
            {
                CarData cd;
                cd.carID = x;
                cd.direction = c.Direction.ToString();
                cd.speed = c.Speed;
                cd.xPos = (int)c.Position.X;
                cd.yPos = (int)c.Position.Y;
                x++;
                cDat.Add(cd);
            }

            saveDat.hLane = hLane;
            saveDat.vLane = vLane;
            saveDat.vProb = (float)vProb;
            saveDat.hProb = (float)hProb;
            if (isTwoWay)
                saveDat.simMode = "Two Way";
            else saveDat.simMode = "Normal";
            saveDat.simNum = simRun;
            saveDat.trafficHLane = TrafficLight[1].DirectionControl.ToString();
            saveDat.trafficVLane = TrafficLight[0].DirectionControl.ToString();
        }

        #endregion
    }
}
